package org.synclib.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.synclib.util.StringUtils;

public abstract class SyncableList extends SyncableObject implements
		Iterable<SyncableObject> {

	private List<SyncableObject> underlyingList = Collections.emptyList();

	private Collection<String> deletedObjectIds = Collections.emptySet();

	public SyncableList(String uuid) {
		super(uuid, System.currentTimeMillis());
	}

	public SyncableList(String uuid, long lastModified) {
		super(uuid, lastModified);
	}

	public void add(SyncableObject object) {
		if (underlyingList.isEmpty()) {
			underlyingList = new ArrayList<SyncableObject>();
		}
		underlyingList.add(object);
		update();
	}

	public SyncableObject remove(int index) {
		SyncableObject removedObject = underlyingList.remove(index);
		if (underlyingList.isEmpty()) {
			underlyingList = Collections.emptyList();
		}
		if (deletedObjectIds.isEmpty()) {
			deletedObjectIds = new HashSet<String>();
		}
		deletedObjectIds.add(removedObject.getUniqueId());
		update();
		return removedObject;
	}

	public boolean remove(SyncableObject object) {
		boolean ret = underlyingList.remove(object);
		if (underlyingList.isEmpty()) {
			underlyingList = Collections.emptyList();
		}
		if (deletedObjectIds.isEmpty()) {
			deletedObjectIds = new HashSet<String>();
		}
		deletedObjectIds.add(object.getUniqueId());
		update();
		return ret;
	}

	private void internalRemove(SyncableObject object) {
		underlyingList.remove(object);
		update();
	}

	public boolean hasDeleted(String uniqueId) {
		return deletedObjectIds.contains(uniqueId);
	}

	public int size() {
		return underlyingList.size();
	}

	public SyncableObject get(int index) {
		return underlyingList.get(index);
	}

	public boolean isEmpty() {
		return underlyingList.isEmpty();
	}

	@Override
	public void applyChanges(List<Change<?>> changes) {
		for (int i = changes.size() - 1; i >= 0; i--) {
			Change<?> c = changes.get(i);
			if (StringUtils.equals(c.getObjectUUID(), getUniqueId())
					&& StringUtils.equals(c.getPropertyName(),
							getListTypeIdentifier())) {
				if (c instanceof AddChange<?>) {
					add((SyncableObject) c.getNewValue());
					changes.remove(i);
				} else if (c instanceof RemoveChange<?>) {
					internalRemove((SyncableObject) c.getOldValue());
					changes.remove(i);
				}
			}
		}
		for (SyncableObject obj : underlyingList) {
			obj.applyChanges(changes);
		}
		super.applyChanges(changes);
	}

	public abstract String getListTypeIdentifier();

	public List<Change<?>> compareWithRemote(SyncableList remote) {
		return compareWithRemote(remote, false);
	}

	public List<Change<?>> compareWithRemote(SyncableList remote, boolean force) {
		List<Change<?>> objectChanges = new ArrayList<Change<?>>(super
				.compareWithRemote(remote, force));

		for (SyncableObject remoteObject : remote) {
			if (this.containsObjectWithUniqueId(remoteObject.getUniqueId())) {
				// How to compare these guys?
				SyncableObject local = getObjectWithUniqueId(remoteObject
						.getUniqueId());
				if (local != null) {
					objectChanges.addAll(local.compareWithRemote(remoteObject));
				}
			} else {
				// If we have deleted it, then we add no change. If we have
				// not deleted it, then it's an addition.
				if (!hasDeleted(remoteObject.getUniqueId())) {
					objectChanges.add(new AddChange<Object>(getUniqueId(),
							getListTypeIdentifier(), remoteObject));
				}
			}
		}

		for (SyncableObject localObject : this) {
			if (!remote.containsObjectWithUniqueId(localObject.getUniqueId())) {
				if (remote.hasDeleted(localObject.getUniqueId())) {
					// Delete it locally, add a RemoveChange.
					objectChanges.add(new RemoveChange<Object>(getUniqueId(),
							getListTypeIdentifier(), localObject));
				}
			}
		}

		return objectChanges;
	}

	private boolean containsObjectWithUniqueId(String uniqueId) {
		for (SyncableObject o : underlyingList) {
			if (StringUtils.equals(o.getUniqueId(), uniqueId)) {
				return true;
			}
		}
		return false;
	}

	private SyncableObject getObjectWithUniqueId(String uniqueId) {
		for (SyncableObject o : underlyingList) {
			if (StringUtils.equals(o.getUniqueId(), uniqueId)) {
				return o;
			}
		}
		return null;
	}

	@Override
	public Iterator<SyncableObject> iterator() {
		final List<SyncableObject> iterables = new ArrayList<SyncableObject>(
				underlyingList);
		return new Iterator<SyncableObject>() {

			private int index = 0;

			@Override
			public boolean hasNext() {
				return index < iterables.size();
			}

			@Override
			public SyncableObject next() {
				SyncableObject syncableObject = iterables.get(index);
				index++;
				return syncableObject;
			}

			@Override
			public void remove() {
				iterables.remove(index);
			}

		};
	}
}
